package com.knowledge.serve.service.websocket;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.knowledge.common.config.GetHttpSessionConfig;
import com.knowledge.model.pojo.WsClientInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.CrossOrigin;
import springfox.documentation.spring.web.json.Json;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
@CrossOrigin("*")
@ServerEndpoint(value = "/chat/{user}",configurator = GetHttpSessionConfig.class)
public class ChatServer {

    //在线人数
    private static int onlineNum=0;

    //线程安全用来存储在线的用户信息
    private static ConcurrentHashMap<String,WsClientInfo> chatInfo = new ConcurrentHashMap<String, WsClientInfo>();

    //定义连接状态存活的时间，暂定30分钟
    private static final  int EXIST_TIME=30;

    private Session session;
    //链接客户端的id信息
    private String user="";

    @OnOpen
    public void onOpen(Session session, @PathParam("user") String user) throws JsonProcessingException {
        this.session = session;
        this.user = user;
        log.info("连接状态信息操作:{},{}",chatInfo,chatInfo.containsKey(user));
        if(chatInfo.containsKey(user)){
            log.info("用户{}已经链接websocket了",user);
            sendMessage("用户"+user+"上线","connect");
            return ;
        }

        //客户端信息实例化对象
        WsClientInfo wsClientInfo = new WsClientInfo();
        wsClientInfo.setToken(user);
        wsClientInfo.setSession(session);
        wsClientInfo.setExistTime(LocalDateTime.now().plusMinutes(EXIST_TIME));
        //添加到安全线程的对象中去
        chatInfo.put(user,wsClientInfo);
//        addOnlineNum();//增加一次在线人数的总人数信息

        log.info("用户{}上线，在线人数:{}",user,getOnlineNum());
        //        sendMessage("用户"+user+"上线","connect");
        publishMessage("用户"+user+"上线了...");
    }

    /**
     * 监听链接关闭的的事件信息
     * @param session
     * @param token
     */
    @OnClose
    public void onClose(Session session,@PathParam("user") String token){
        log.info("触发关闭请求的方法,{}",token);
        //找到链接关闭的用户并且直接从链接对象中删除
        if(ObjectUtil.isNotEmpty(token) && chatInfo.containsKey(token)){
            chatInfo.remove(token);
//            ChatServer.subOnlineNum();
            log.info("{},断开了websocket链接",token);
            publishMessage(token+"下线了...");
        }
    }

    /**
     * @desc 监听客户端发送过来的消息
     * @param session
     * @param token
     * @param message
     */
    @OnMessage
    public void onMessage(Session session,@PathParam("user") String token,String message) throws JsonProcessingException {
        log.info("客户端传递过来的消息是:{},{}",message,token);
        //心跳检测机制，反馈给用户一个基本信息
        WsClientInfo wsClientInfo = chatInfo.get(token);

        if(LocalDateTime.now().compareTo(wsClientInfo.getExistTime())>0){
            log.info("链接状态已经过期直接关闭");
            chatInfo.remove(token);
        }

        //心跳检测机制，通过传递过来的用户，我们给用户一个反馈
        if("ping".equals(message)){
            wsClientInfo.setExistTime(LocalDateTime.now().plusMinutes(EXIST_TIME));
            if(wsClientInfo.getSession().isOpen()){
                log.info("心跳检测，pang");
                wsClientInfo.getSession().getAsyncRemote().sendText("pang");
            }
            return ;
        }

        JSONObject parse = (JSONObject) JSON.parse(message);
        //进入聊天阶段的逻辑
        log.info("解析数据接受过来的消息:{}",parse);

        //聊天开始，开始尝试发送消息解析消息内容
    }

    /**
     * @desc 发送消息的提示信息
     * @param message
     */
    public void sendMessage(String message,String type) throws JsonProcessingException {
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("msg",message);
        hashMap.put("onlineNum",getOnlineNum());
        hashMap.put("type",type);
        ObjectMapper mapper = new ObjectMapper();
        String data = mapper.writeValueAsString(hashMap);
        try{
            //发送消息给当前连接的用户信息
            this.session.getAsyncRemote().sendText(data);
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    public void publishMessage(String message){
        log.info("向全体发送消息:");
        if(chatInfo.values().size() == 0){
            log.info("没有人在线，无需发送消息");
            return ;
        }
        chatInfo.values().forEach(item->{
            //向每个用户发送文本信息。这里getAsyncRemote()解释一下，向用户发送文本信息有两种方式，
            // 一种是getBasicRemote，一种是getAsyncRemote
            //区别：getAsyncRemote是异步的，不会阻塞，而getBasicRemote是同步的，会阻塞，由于同步特性，第二行的消息必须等待第一行的发送完成才能进行。
            // 而第一行的剩余部分消息要等第二行发送完才能继续发送，所以在第二行会抛出IllegalStateException异常。所以如果要使用getBasicRemote()同步发送消息
            // 则避免尽量一次发送全部消息，使用部分消息来发送，可以看到下面sendMessageToTarget方法内就用的getBasicRemote，因为这个方法是根据用户id来私发的，所以不是全部一起发送。
            HashMap<String, Object> hashMap = new HashMap<>();
            hashMap.put("msg",message);
            hashMap.put("onlineNum",getOnlineNum());
            hashMap.put("type","publish");
            item.getSession().getAsyncRemote().sendText(JSON.toJSONString(hashMap));
        });
    }

    //在线人数的相关方法
    public static synchronized int getOnlineNum(){
        onlineNum = ChatServer.chatInfo.size();
        return onlineNum;
    }
    public static synchronized void addOnlineNum(){
        ChatServer.onlineNum++;
//        ChatServer.onlineNum = ChatServer.chatInfo.size();
    }
    public static synchronized void subOnlineNum(){
        ChatServer.onlineNum--;
    }

    /**
     * @desc 获取当前在线的用户信息列表
     * @return
     */
    public static synchronized ConcurrentHashMap<String,WsClientInfo> getChatInfo(){
        return chatInfo;
    }
}
